/*
 *   The hash-tree for storing the XML file, the structure can speedup the query
 * 
 *       done by Lee in 2013/6/29
 */
package lee.base.structure;

import java.util.ArrayList;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class MyHashTree {
	
	private String symbolRefVariable = null;
	private String symbolSplit = null;
	private MyNode treeHead = null;  //the root node of tree
	
	/**
	 * The constructor of MyHashTree
	 * @param symbolRefVariable : the symbol of first word in referenced variable
	 * @param symbolSplit : the symbol for splitting each attribute in variable
	 */
	public MyHashTree(String symbolRefVariable, String symbolSplit) {
		this.symbolRefVariable = symbolRefVariable;
		this.symbolSplit = symbolSplit;
	}
	
	/**
	 * store full parsed-XML data - document to MyHashTree, based on 
	 * recursing each node for storing, if the string of attribute 
	 * contains "#", return null 
	 * @param attr : the attribute of new node - String
	 * @param node : the new node - Node
	 * @return new node - MyNode or null
	 */
	private MyNode _001_storeDocRecurse(String attr, Node node) {
		if(attr.contains("#")) {  //ignore the attribute containing the "#"
			return null;
		}
		MyNode myNode = new MyNode();
		Node getNode = null;
		int index = 0;
		//set attribute name of node
		myNode.ACT001_addAttribute(attr);
		//add list of child node
		MyNode newNode = null;
		if(node.getChildNodes().getLength() < 2) {  //set value when arriving the leave node
			myNode.ACT002_addValue(node.getChildNodes().item(0).getNodeValue());
		}
		else {  //add all next node
			index = 0;
			while(index < node.getChildNodes().getLength()) {
				getNode = node.getChildNodes().item(index);
				newNode = this._001_storeDocRecurse(getNode.getNodeName() ,getNode);
				if(newNode != null) {
				    myNode.ACT003_addAttrNext(getNode.getNodeName(), newNode);
				}
				index = index + 1;
			}
		}
		return myNode;
	}
	/**
	 * trace and get specific node according to the given combined attribute, the this type
	 * of node does not exist, return null
	 * @param attrCombine - String
	 * @return MyNode or null
	 */
	private MyNode _002_traceGetNode(String attrCombine, MyNode specificHead) {
		MyNode getNode = null;
		if(specificHead == null) {
			getNode = this.treeHead;
		}
		else {
			getNode = specificHead;
		}
		boolean isFail = false;
		//process the variable attribute
		ArrayList<String> listAttrFinal = this._003_traceAndGetConstantAttribute(attrCombine, getNode);
		if(listAttrFinal == null) {
			isFail = true;
		}
		//start to trace node
		int index = 0;
		while(!isFail && index < listAttrFinal.size()) {
			if(getNode == null || getNode.GET003_attribute().compareTo(listAttrFinal.get(index)) != 0) { //the node does not exist
				getNode = null;
				isFail = true;
			}
			else {  //find target node and trace to next node
			    index = index + 1;
			    if(index < listAttrFinal.size()) {
			    	getNode = getNode.GET002_NextNode(listAttrFinal.get(index));
			    }
			}
		}
		return getNode;
	}
	/**
	 * trace each attribute to check whether existing some variable attribute that
	 * referencing to other attribute, if so, replace real attribute to those
	 * @param attrCombine - String
	 * @return ArrayList or null
	 */
	protected ArrayList<String> _003_traceAndGetConstantAttribute(String attrCombine, MyNode specificHead) {
		String[] list_attr = attrCombine.split(this.symbolSplit);
		ArrayList<String> listAttr = new ArrayList<String>();
		int index = 0;
		String addName = null;
		while(listAttr != null && index < list_attr.length) {
			addName = list_attr[index];
			if(addName.contains(this.symbolRefVariable)) {
				addName = addName.replace(this.symbolRefVariable, "");
				addName = this.ACT002_getSpecificValue(specificHead.GET003_attribute() + this.symbolSplit + addName, null);
				if(addName == null) {  //error reference variable name
					listAttr = null;
				}
				else {
					String[] listVAttr = addName.split(this.symbolSplit);
					int indexAdd = 0;
					while(indexAdd < listVAttr.length) {
						listAttr.add(listVAttr[indexAdd]);
						indexAdd = indexAdd + 1;
					}
				}
			}
			else {
				listAttr.add(addName);
			}
			index = index + 1;
		}
		return listAttr;
	}
	
	/**
	 * transform object of document to MyHashTree
	 * @param Document
	 */
	public void ACT001_storeFromDocument(Document doc) {
		NodeList docHead = doc.getChildNodes();
		this.treeHead = this._001_storeDocRecurse(docHead.item(0).getNodeName(), docHead.item(0));
	}
	/**
	 * According to combined attribute, get the value, if this type of attribute does
	 * not exist, return null
	 * @param attrCombine - String
	 * @return String or null
	 */
	public String ACT002_getSpecificValue(String attrCombine, MyNode newHead) {
		String getStr = null;
		MyNode targetNode = this._002_traceGetNode(attrCombine, newHead);
		if(targetNode != null) {
			getStr = targetNode.GET001_value();
		}
		return getStr;
	}
	/**
	 * According to combined attribute, get the node, if this type of attribute does
	 * not exist, return null
	 * @param attrCombine - String
	 * @return MyNode or null
	 */
	public MyNode ACT003_getSpecificNode(String attrCombine, MyNode newHead) {
		MyNode targetNode = this._002_traceGetNode(attrCombine, newHead);
		return targetNode;
	}
	/**
	 * According to combined attribute, set the value of node,if setting is 
	 * successful, return true; if this type of attribute does
	 * not exist or the node is not at leave, the setting is failed and return false
	 * @param attrCombine - String
	 * @param setValue - String
	 * @return boolean
	 */
	public boolean ACT004_setSpecificValue(String attrCombine, String setValue) {
   	    boolean isSuccessful = false;
   	    MyNode targetNode = this._002_traceGetNode(attrCombine, null);
   	    if(targetNode != null && targetNode.GET001_value() != null) {
   		    targetNode.SET001_value(setValue);
   	        isSuccessful = true;
   	    }
   	    return isSuccessful;
    }
	/**
	 * get attribute name for the head node in this tree
	 * @return String
	 */
	public String ACT005_getHeadAttributeName() {
		return this.treeHead.GET003_attribute();
	}
	/**
	 * get head node for this tree
	 * @return MyNode
	 */
	public MyNode ACT006_getHeadNode() {
		return this.treeHead;
	}
}
